Skip to content

feat(workflow-executor): add ReadRecordStepExecutor#1497

Merged
Scra3 merged 37 commits intofeat/prd-214-setup-workflow-executor-packagefrom
feat/prd-218-read-record-step-executor
Mar 19, 2026
Merged

feat(workflow-executor): add ReadRecordStepExecutor#1497
Scra3 merged 37 commits intofeat/prd-214-setup-workflow-executor-packagefrom
feat/prd-218-read-record-step-executor

Conversation

@Scra3
Copy link
Member

@Scra3 Scra3 commented Mar 18, 2026

Summary

  • Add ReadRecordStepExecutor — first AI-task step executor that reads fields from a record
  • AI-based record selection when multiple records are available (via select-record tool)
  • AI-based field selection via read-selected-record-field tool (supports single/multiple fields, displayName + fieldName resolution)
  • New error classes: NoRecordsError, NoReadableFieldsError
  • Relationship fields excluded from readable fields (ISO frontend)
  • Previous steps context included in both record selection and field selection prompts

Test plan

  • Single record, single/multiple field reads
  • Field resolution by displayName
  • Field not found returns per-field error (no global failure)
  • Relationship fields excluded from tool schema
  • No readable fields → error status
  • Multi-record AI selection (first and second record)
  • No records → error status
  • Model error / malformed tool call / missing tool call → error status
  • RunStore errors propagate (getRecords, saveStepExecution)
  • Input stepHistory immutability
  • Previous steps context in messages
  • Default prompt fallback
  • saveStepExecution arguments (executionParams, executionResult, selectedRecordRef)

fixes PRD-218

🤖 Generated with Claude Code

Note

Add ReadRecordStepExecutor for AI-driven record field reading in workflow execution

  • Adds ReadRecordStepExecutor which uses LLM tool-calling to select relevant fields, fetches only those fields via AgentPort.getRecord, and persists results as a read-record step execution.
  • Renames StepHistory to StepOutcome and CollectionRef to CollectionSchema throughout types, ports, and adapters; the execute() method on all step executors is now parameterless and reads step metadata from ExecutionContext.
  • AgentPort.getRecord gains an optional fieldNames parameter to limit returned fields; getActions is removed from the port interface.
  • RunStore drops record-centric methods (getRecords, getRecord, saveRecord, getStepExecution), retaining only step execution persistence.
  • BaseStepExecutor.buildPreviousStepsMessages now emits separate Input: and Output: lines per prior step instead of a single Result: line.
  • Risk: Breaking public API changes — removed exports (StepHistory, CollectionRef, StepRecord, StepCategory, getActions) and renamed types require consumers to update imports and port implementations.

Macroscope summarized 6dac969.

@linear
Copy link

linear bot commented Mar 18, 2026

@qltysh
Copy link

qltysh bot commented Mar 18, 2026

Qlty

Coverage Impact

Unable to calculate total coverage change because base branch coverage was not found.

Modified Files with Diff Coverage (8)

RatingFile% DiffUncovered Line #s
New file Coverage rating: A
...s/workflow-executor/src/executors/read-record-step-executor.ts100.0%
New file Coverage rating: A
...ges/workflow-executor/src/executors/condition-step-executor.ts100.0%
New file Coverage rating: A
packages/workflow-executor/src/executors/base-step-executor.ts100.0%
New file Coverage rating: A
.../workflow-executor/src/adapters/forest-server-workflow-port.ts100.0%
New file Coverage rating: A
...ages/workflow-executor/src/adapters/agent-client-agent-port.ts100.0%
New file Coverage rating: A
packages/workflow-executor/src/types/step-execution-data.ts100.0%
New file Coverage rating: A
packages/workflow-executor/src/errors.ts100.0%
New file Coverage rating: A
packages/workflow-executor/src/index.ts100.0%
Total100.0%
🚦 See full report on Qlty Cloud »

🛟 Help
  • Diff Coverage: Coverage for added or modified lines of code (excludes deleted files). Learn more.

  • Total Coverage: Coverage for the whole repository, calculated as the sum of all File Coverage. Learn more.

  • File Coverage: Covered Lines divided by Covered Lines plus Missed Lines. (Excludes non-executable lines including blank lines and comments.)

    • Indirect Changes: Changes to File Coverage for files that were not modified in this PR. Learn more.

Base automatically changed from feat/prd-216-condition-step-executor to feat/prd-214-setup-workflow-executor-package March 18, 2026 13:00
@Scra3 Scra3 force-pushed the feat/prd-218-read-record-step-executor branch from f6b101a to 71c4104 Compare March 18, 2026 13:06
}

export interface RecordData extends RecordRef {
stepIndex: number;
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

want to save the stepIndex to be sure to find the right o step in the workflow

@Scra3 Scra3 force-pushed the feat/prd-218-read-record-step-executor branch from 55b265f to 25fdd06 Compare March 18, 2026 14:41
import type { StepExecutionData } from '../types/step-execution-data';

export interface RunStore {
getRecords(): Promise<RecordData[]>;
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

useless

alban bertolini and others added 20 commits March 18, 2026 18:08
Implement the read-record step executor that allows AI to read fields
from a record. Includes AI-based record selection when multiple records
are available, and field selection via the read-selected-record-field tool.

fixes PRD-218

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…epExecutor

- Extract toRecordIdentifier helper to avoid duplicate template string
- Add test: AI selects non-existent record identifier
- Add saveStepExecution not-called assertions to no-records and no-readable-fields tests

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ce symmetry

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…to single FieldReadResult

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…elds across FieldRead types

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…c method

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
… array schema

- Remove unused FieldReadResults type alias
- Rename read-selected-record-field to read-selected-record-fields
- fieldNames is now always string[] (no more single string union)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Move FieldReadResult types from executor to step-execution-data.ts
- Add ReadRecordStepExecutionData with typed executionParams/executionResult
- Use type: 'read-record' discriminator instead of generic 'ai-task'
- Export new types from index.ts

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…equired

- executionParams and executionResult are now required (align with ReadRecord)
- Reorganize step-execution-data.ts by section (Condition, ReadRecord, Generic)
- Fix base-step-executor tests for required fields

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Remove unused fieldNames variable in buildReadFieldTool
- Fix prettier formatting
- Fix duplicate test title in base-step-executor

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ypes

Split the old CollectionRef → RecordData hierarchy into independent
schema types (CollectionSchema, FieldSchema) and data types (RecordRef,
RecordData). Schema is now sourced from WorkflowPort.getCollectionSchema,
records from AgentPort/RunStore. Remove dead fields (type,
referencedCollectionName) and dead code (availableRecords).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ove AgentPort.getActions

Actions metadata is structural info belonging to CollectionSchema, not
an AgentPort concern. Remove the redundant getActions method from
AgentPort and rename ActionRef to ActionSchema for naming consistency.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Remove getRecord, saveRecord, and getStepExecution from RunStore
interface — none are called in production code. Records are accessed
via getRecords, step executions via getStepExecutions/saveStepExecution.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ord + LoadRelatedRecordStepExecutionData

Records are now accessed via ExecutionContext.baseRecord (trigger record) and
LoadRelatedRecordStepExecutionData entries in the RunStore, removing the
separate getRecords() method.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add isExecutedStepOnExecutor type guard and ExecutedStepExecutionData
union type. buildStepSummary now outputs executionResult so subsequent
steps can access field values read by earlier steps.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
executionParams is the input (AI decision), executionResult is the
output. Rename the summary label accordingly for clarity.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The field holds a RecordRef, not a RecordData. Rename for clarity.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
alban bertolini and others added 3 commits March 18, 2026 18:08
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ues via AgentPort

The orchestrator only sends a record reference (collection + recordId), not client data.
ReadRecordStepExecutor now fetches values through agentPort.getRecord() when needed.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@Scra3 Scra3 force-pushed the feat/prd-218-read-record-step-executor branch from 35a89ec to 33e5552 Compare March 18, 2026 17:12
alban bertolini and others added 3 commits March 18, 2026 18:40
…ta type

- Only catch WorkflowExecutorError in execute(), re-throw infrastructure errors
- Redefine RecordData as Omit<RecordRef, 'stepIndex'> & { values }
- Change LoadRelatedRecordStepExecutionData.record to RecordRef
- Use RecordData directly in AgentPort (remove AgentRecord alias)
- Update tests: model errors propagate, add RecordNotFoundError test

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…Context

Make ExecutionContext generic with TStep/THistory defaults so executors
read step and stepHistory from context instead of execute() arguments.
Also rename selectRecord → selectRecordRef and getAvailableRecords →
getAvailableRecordRefs for consistency.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Rename agentRecord → recordData and readFieldValues → formatFieldResults
for clarity.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
fieldNames: string[],
): FieldReadResult[] {
return fieldNames.map(name => {
const field = schema.fields.find(f => f.fieldName === name || f.displayName === name);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Medium executors/read-record-step-executor.ts:167

In formatFieldResults, the field lookup uses f.fieldName === name || f.displayName === name, checking fieldName before displayName. Since the AI tool in buildReadFieldTool is told to use displayNames as possible values, a field with displayName = "Status" could be shadowed by another field with fieldName = "Status", causing the wrong field's value to be returned.

-      const field = schema.fields.find(f => f.fieldName === name || f.displayName === name);
+      const field = schema.fields.find(f => f.displayName === name || f.fieldName === name);
🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file packages/workflow-executor/src/executors/read-record-step-executor.ts around line 167:

In `formatFieldResults`, the field lookup uses `f.fieldName === name || f.displayName === name`, checking `fieldName` before `displayName`. Since the AI tool in `buildReadFieldTool` is told to use `displayNames` as possible values, a field with `displayName = "Status"` could be shadowed by another field with `fieldName = "Status"`, causing the wrong field's value to be returned.

Evidence trail:
packages/workflow-executor/src/executors/read-record-step-executor.ts lines 134-159 (buildReadFieldTool telling AI to use displayNames) and lines 161-176 (formatFieldResults using `f.fieldName === name || f.displayName === name`); packages/workflow-executor/src/types/record.ts lines 5-9 (FieldSchema with separate fieldName and displayName).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not fixed — both lookup orders have the same shadowing risk in opposite directions. The current order is ISO frontend. In practice, fieldName and displayName are almost never the same string across different fields, so the collision scenario is theoretical.

alban bertolini and others added 11 commits March 18, 2026 19:49
…ep summary

Add null check before stringifying executionParams in buildStepSummary,
since AiTaskStepExecutionData.executionParams is optional. Also fix
prettier issue on selectRecordRef signature.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ils import

ServerUtils is now properly exported from @forestadmin/forestadmin-client.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add test for undefined executionParams in step summary and instantiate
NoRecordsError to meet diff coverage threshold.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…pExecution

Replace separate step/stepHistory fields with currentStep: StepRecord
for consistency with the existing StepRecord type.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
… and add stepIndex to StepDefinition

Executors now construct StepHistory internally instead of receiving it
via context. Also removes unused StepCategory type and simplifies
selectedRecordRef spread.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ed tool func callbacks

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…tract id/stepIndex from StepDefinition

StepHistory → StepOutcome for clarity (it's the outcome of a step, not history).
Moved id/stepIndex from StepDefinition (definition concern) to ExecutionContext
and PendingStepExecution (instance concern). Renamed StepRecord to Step.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Resolve AI display names to real field names before calling getRecord,
so only the needed fields are fetched from the datasource.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…lved

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ame extraction

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@Scra3 Scra3 merged commit c9877fe into feat/prd-214-setup-workflow-executor-package Mar 19, 2026
30 checks passed
@Scra3 Scra3 deleted the feat/prd-218-read-record-step-executor branch March 19, 2026 14:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants